﻿using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Interop;
using Standard;
using HANDLE_MESSAGE = System.Collections.Generic.KeyValuePair<Standard.WM, Standard.MessageHandler>;

namespace ChromeWindow;

public class SimpleChromeWindow : Window
{
    private IntPtr WindowHandle;
    private readonly List<HANDLE_MESSAGE> MessageTable;
    private static readonly Type TypeOfThis = typeof(SimpleChromeWindow);
    private static SimpleChromeWindow GetView(DependencyObject d) => (SimpleChromeWindow)d;


    protected SimpleChromeWindow()
    {
        Loaded += OnLoaded;
        Dpi = this.GetDpi();
        PaddedBorderThicknessCache = null;
        MessageTable = new List<HANDLE_MESSAGE>
        {
            new(WM.ACTIVATE, HandleActivate),
            new(WM.NCCALCSIZE, HandleNCCaleSize),
            new(WM.NCHITTEST, HandleNCHitTest)
        };
    }


    #region 属性

    #region Dpi

    private DpiScale Dpi;

    protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi)
    {
        Dpi = newDpi;
        base.OnDpiChanged(oldDpi, newDpi);
    }

    #endregion


    #region OuterResizeBorderThicknessProperty

    /// <summary>
    /// 窗口外边调整尺寸的边框大小（单位：Point）
    /// </summary>
    private Thickness OuterResizeBorderThickness
    {
        get
        {
            var resizeBorder = SystemParameters.WindowResizeBorderThickness;
            var paddedBorder = PaddedBorderThickness;

            return new Thickness
            (
                resizeBorder.Left + paddedBorder.Left,
                resizeBorder.Top + paddedBorder.Top,
                resizeBorder.Right + paddedBorder.Right,
                resizeBorder.Bottom + paddedBorder.Bottom
            );
        }
    }

    /// <summary>
    /// 窗口外边调整尺寸的边框大小（单位：Pixel）
    /// </summary>
    private Thickness OuterResizeBorderThicknessPixel
        => DpiHelper.LogicalThicknessToDevice(OuterResizeBorderThickness, Dpi.DpiScaleX, Dpi.DpiScaleY);

    #endregion


    #region PaddedBorderThickness

    /// <summary>
    /// 返回带标题窗口的边框填充量（以Point为单位）。Windows XP/2000：不支持此值。
    /// </summary>
    private Thickness PaddedBorderThickness
        => DpiHelper.DeviceThicknessToLogical(PaddedBorderThicknessPixel, Dpi.DpiScaleX, Dpi.DpiScaleY);

    /// <summary>
    /// 返回带标题窗口的边框填充量（以像素为单位）。Windows XP/2000：不支持此值。
    /// 参考：https://www.cnblogs.com/dino623/p/problems_of_WindowChrome.html
    ///      https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getsystemmetrics
    /// Firefox源码： https://github.com/mozilla/gecko-dev/blob/master/widget/windows/nsWindow.cpp
    /// </summary>
    private Thickness PaddedBorderThicknessPixel
    {
        [SecurityCritical]
        get
        {
            const int SM_CXPADDEDBORDER = 92;

            if (PaddedBorderThicknessCache != null) return PaddedBorderThicknessCache.Value;

            var paddedBorder = NativeMethods.GetSystemMetrics(SM_CXPADDEDBORDER);
            var frameSize = new Size(paddedBorder, paddedBorder);
            var frameSizeInDips = DpiHelper.DeviceSizeToLogical(frameSize, Dpi.DpiScaleX, Dpi.DpiScaleY);
            PaddedBorderThicknessCache = new Thickness(frameSizeInDips.Width, frameSizeInDips.Height, frameSizeInDips.Width, frameSizeInDips.Height);
            return PaddedBorderThicknessCache.Value;
        }
    }

    private Thickness? PaddedBorderThicknessCache;

    #endregion


    #region InnerResizeBorderThicknessProperty

    /// <summary>
    /// 窗口内调整尺寸的边框大小（单位：Point）
    /// </summary>
    public Thickness InnerResizeBorderThickness
    {
        get => (Thickness)GetValue(InnerResizeBorderThicknessProperty);
        set => SetValue(InnerResizeBorderThicknessProperty, value);
    }

    public static readonly DependencyProperty InnerResizeBorderThicknessProperty =
        DependencyProperty.Register(
            nameof(InnerResizeBorderThickness),
            typeof(Thickness),
            TypeOfThis,
            new PropertyMetadata(new Thickness(4d)));

    /// <summary>
    /// 窗口内调整尺寸的边框大小（单位：Pixel）
    /// </summary>
    private Thickness InnerResizeBorderThicknessPixel
        => DpiHelper.LogicalThicknessToDevice(InnerResizeBorderThickness, Dpi.DpiScaleX, Dpi.DpiScaleY);

    #endregion


    #region ResizeBorderThickness

    /// <summary>
    /// 调整尺寸边框的大小（单位：Point）
    /// </summary>
    private Thickness ResizeBorderThickness
        => DpiHelper.DeviceThicknessToLogical(ResizeBorderThicknessPixel, Dpi.DpiScaleX, Dpi.DpiScaleY);

    /// <summary>
    /// 调整尺寸边框的大小（单位：像素）
    /// </summary>
    private Thickness ResizeBorderThicknessPixel
    {
        get
        {
            var outer = OuterResizeBorderThicknessPixel;
            var inner = InnerResizeBorderThicknessPixel;
            return new Thickness(
                outer.Left + inner.Left,
                outer.Top + inner.Top,
                outer.Right + inner.Right,
                outer.Bottom + inner.Bottom);
        }
    }

    #endregion


    #region CalculateOuterResizeBorderThicknessProperty

    /// <summary>
    /// 经过计算后的窗口外围调整尺寸的边框大小（单位：Point）
    /// </summary>
    public Thickness CalculateOuterResizeBorderThickness
    {
        get => (Thickness)GetValue(CalculateOuterResizeBorderThicknessProperty);
        private set => SetValue(CalculateOuterResizeBorderThicknessProperty, value);
    }

    public static readonly DependencyProperty CalculateOuterResizeBorderThicknessProperty =
        DependencyProperty.Register(
            nameof(CalculateOuterResizeBorderThickness),
            typeof(Thickness),
            TypeOfThis,
            new PropertyMetadata(new Thickness(0)));

    #endregion


    #region NonclientHitTestResult

    /// <summary>
    /// 非客户区命中测试结果
    /// </summary>
    public static readonly DependencyProperty NonClientHitTestResultProperty =
        DependencyProperty.RegisterAttached(
            "NonClientHitTestResult",
            typeof(NonClientHitTestResult),
            TypeOfThis,
            new FrameworkPropertyMetadata(NonClientHitTestResult.Client,
                FrameworkPropertyMetadataOptions.Inherits));

    public static NonClientHitTestResult GetNonClientHitTestResult(IInputElement inputElement)
    {
        if (inputElement is not DependencyObject obj)
        {
            throw new ArgumentException("inputElement必须是DependencyObject");
        }

        return (NonClientHitTestResult)obj.GetValue(NonClientHitTestResultProperty);
    }

    public static void SetNonClientHitTestResult(IInputElement inputElement, NonClientHitTestResult value)
    {
        if (inputElement is not DependencyObject obj)
        {
            throw new ArgumentException("inputElement必须是DependencyObject");
        }

        obj.SetValue(NonClientHitTestResultProperty, value);
    }

    #endregion

    #endregion


    #region 消息处理

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        WindowHandle = new WindowInteropHelper(this).Handle;
        HwndSource.FromHwnd(WindowHandle)?.AddHook(Hook);
    }

    private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        Assert.AreEqual(hwnd, WindowHandle);

        var message = (WM)msg;
        foreach (var handlePair in MessageTable.Where(pair => pair.Key == message))
        {
            return handlePair.Value(message, wParam, lParam, out handled);
        }

        return IntPtr.Zero;
    }

    private IntPtr HandleActivate(WM umsg, IntPtr wparam, IntPtr lparam, out bool handled)
    {
        // 在窗口激活时扩展窗口的边框，参考：https://learn.microsoft.com/zh-cn/windows/win32/dwm/customframe

        // 扩展之后可以保留窗口四周的阴影
        var extend = OuterResizeBorderThickness;
        var nonClientArea = DpiHelper.LogicalThicknessToDevice(extend, Dpi.DpiScaleX, Dpi.DpiScaleY);
        var dwmMargin = new MARGINS
        {
            // err on the side of pushing in glass an extra pixel.
            cxLeftWidth = (int)Math.Ceiling(nonClientArea.Left),
            cxRightWidth = (int)Math.Ceiling(nonClientArea.Right),
            cyTopHeight = (int)Math.Ceiling(nonClientArea.Top),
            cyBottomHeight = (int)Math.Ceiling(nonClientArea.Bottom),
        };

        NativeMethods.DwmExtendFrameIntoClientArea(WindowHandle, ref dwmMargin);

        handled = true;
        return IntPtr.Zero;
    }

    private IntPtr HandleNCCaleSize(WM umsg, IntPtr wParam, IntPtr lParam, out bool handled)
    {
        // 在计算窗口工作区的大小和位置时触发，参考：https://learn.microsoft.com/zh-cn/windows/win32/winmsg/wm-nccalcsize

        // 让工作区直接填满整个窗口
        handled = true;
        var retVal = IntPtr.Zero;
        if (wParam.ToInt32() != 0) // wParam == TRUE
        {
            retVal = new IntPtr((int)WVR.REDRAW);
        }

        return retVal;
    }

    private IntPtr HandleNCHitTest(WM umsg, IntPtr wparam, IntPtr lparam, out bool handled)
    {
        // 参考：https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-nchittest

        var mousePosScreen = new Point(Utility.GET_X_LPARAM(lparam), Utility.GET_Y_LPARAM(lparam));
        var windowRect = GetWindowRect();

        var mousePosWindow = mousePosScreen;
        mousePosWindow.Offset(-windowRect.X, -windowRect.Y);
        mousePosWindow = DpiHelper.DevicePixelsToLogical(mousePosWindow, Dpi.DpiScaleX, Dpi.DpiScaleY);

        var inputElement = InputHitTest(mousePosWindow);
        if (inputElement is DependencyObject)
        {
            var result = GetNonClientHitTestResult(inputElement);
            if (result != NonClientHitTestResult.Nowhere)
            {
                handled = true;
                return new IntPtr((int)result);
            }
        }

        handled = true;
        return new IntPtr((int)NonClientHitTestResult.Nowhere);
    }

    #endregion


    /// <summary>
    /// Get the bounding rectangle for the window in physical coordinates.
    /// </summary>
    /// <returns>The bounding rectangle for the window.</returns>
    /// <SecurityNote>
    ///   Critical : Calls critical methods
    /// </SecurityNote>
    [SecurityCritical]
    private Rect GetWindowRect()
    {
        // Get the window rectangle.
        var windowPosition = NativeMethods.GetWindowRect(WindowHandle);
        return new Rect(windowPosition.Left, windowPosition.Top, windowPosition.Width, windowPosition.Height);
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        UpdateMargin();
    }

    protected override void OnStateChanged(EventArgs e)
    {
        UpdateMargin();
        base.OnStateChanged(e);
    }

    private void UpdateMargin()
    {
        if (WindowState == WindowState.Maximized)
        {
            var leftRight = (SystemParameters.MaximizedPrimaryScreenWidth - SystemParameters.WorkArea.Width) / 2d;
            var topBottom = (SystemParameters.MaximizedPrimaryScreenHeight - SystemParameters.WorkArea.Height) / 2d;

            CalculateOuterResizeBorderThickness = new Thickness(leftRight, topBottom, leftRight, topBottom);
        }
        else
        {
            CalculateOuterResizeBorderThickness = new Thickness(0);
        }
    }
}